/*
 * Copyright 2008-2010 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.btrace.client;

import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import sun.misc.Signal;
import sun.misc.SignalHandler;
import com.sun.btrace.CommandListener;
import com.sun.btrace.comm.Command;
import com.sun.btrace.comm.DataCommand;
import com.sun.btrace.comm.ErrorCommand;
import com.sun.btrace.comm.ExitCommand;
import com.sun.btrace.util.Messages;

/**
 * This is the main class for a simple command line BTrace client. It is
 * possible to create a GUI client using the Client class.
 * 
 * @author A. Sundararajan
 */
public final class Main {
	public static volatile boolean exiting;
	public static final boolean DEBUG;
	public static final boolean TRACK_RETRANSFORM;
	public static final boolean UNSAFE;
	public static final boolean DUMP_CLASSES;
	public static final String DUMP_DIR;
	public static final String PROBE_DESC_PATH;
	public static final int BTRACE_DEFAULT_PORT = 2020;
	private static String pwd = "";

	private static final Console con;
	private static final PrintWriter out;
	static {
		DEBUG = Boolean.getBoolean("com.sun.btrace.debug");
		if (isDebug())
			debugPrint("btrace debug mode is set");
		TRACK_RETRANSFORM = Boolean
				.getBoolean("com.sun.btrace.trackRetransforms");
		if (isDebug() && TRACK_RETRANSFORM)
			debugPrint("trackRetransforms flag is set");
		UNSAFE = Boolean.getBoolean("com.sun.btrace.unsafe");
		if (isDebug() && UNSAFE)
			debugPrint("btrace unsafe mode is set");
		DUMP_CLASSES = Boolean.getBoolean("com.sun.btrace.dumpClasses");
		if (isDebug() && DUMP_CLASSES)
			debugPrint("dumpClasses flag is set");
		DUMP_DIR = System.getProperty("com.sun.btrace.dumpDir", ".");
		if (DUMP_CLASSES) {
			if (isDebug())
				debugPrint("dumpDir is " + DUMP_DIR);
		}
		PROBE_DESC_PATH = System.getProperty("com.sun.btrace.probeDescPath",
				".");
		con = System.console();
		out = (con != null) ? con.writer() : new PrintWriter(System.out);
	}

	public static void main(String[] args) {
		if (args.length < 2) {
			usage();
		}

		int port = BTRACE_DEFAULT_PORT;
		String classPath = ".";
		String includePath = null;

		int count = 0;
		boolean portDefined = false;
		boolean classpathDefined = false;
		boolean includePathDefined = false;

		for (;;) {
			if (args[count].charAt(0) == '-') {
				if (args.length <= count + 1) {
					usage();
				}
				if (args[count].equals("-p") && !portDefined) {
					try {
						port = Integer.parseInt(args[++count]);
						if (isDebug())
							debugPrint("accepting port " + port);
					} catch (NumberFormatException nfe) {
						usage();
					}
					portDefined = true;
				} else if ((args[count].equals("-cp") || args[count]
						.equals("-classpath")) && !classpathDefined) {
					classPath = args[++count];
					if (isDebug())
						debugPrint("accepting classpath " + classPath);
					classpathDefined = true;
				} else if (args[count].equals("-I") && !includePathDefined) {
					includePath = args[++count];
					if (isDebug())
						debugPrint("accepting include path " + includePath);
					includePathDefined = true;
				} else {
					usage();
				}
				count++;
				if (count >= args.length) {
					break;
				}
			} else {
				break;
			}
		}

		if (!portDefined) {
			if (isDebug())
				debugPrint("assuming default port " + port);
		}

		if (!classpathDefined) {
			if (isDebug())
				debugPrint("assuming default classpath '" + classPath + "'");
		}

		if (args.length < (count + 1)) {
			usage();
		}

		String pid = args[count];
		String fileName = args[count + 1];
		String[] btraceArgs = new String[args.length - count];
		if (btraceArgs.length > 0) {
			System.arraycopy(args, count, btraceArgs, 0, btraceArgs.length);
		}

		try {
			Client client = new Client(port, PROBE_DESC_PATH, DEBUG,
					TRACK_RETRANSFORM, UNSAFE, DUMP_CLASSES, DUMP_DIR);
			if (!new File(fileName).exists()) {
				errorExit("File not found: " + fileName, 1);
			}

			byte[] code = client.compile(fileName, classPath, includePath);
			if (code == null) {
				errorExit("BTrace compilation failed", 1);
			}

			client.attach(pid);

			Console con = System.console();
			if (con == null) {
				System.out.println("can't get console!!!");
				return;
			}
			String pwd = String.valueOf(con.readPassword("[%s]",
					"enter password:"));
			if (isDebug())
				debugPrint("btrace will login as password" + pwd);
			client.setPassword(pwd);
			if (!client.login()) {
				System.out.println("fail to login, exit!!!");
			}
			registerExitHook(client);
			if (con != null) {
				registerSignalHandler(client);
			}
			if (isDebug())
				debugPrint("submitting the BTrace program");
			client.submit(fileName, code, btraceArgs,
					createCommandListener(client));
		} catch (IOException exp) {
			errorExit(exp.getMessage(), 1);
		}
	}

	private static CommandListener createCommandListener(Client client) {
		return new CommandListener() {
			public void onCommand(Command cmd) throws IOException {
				int type = cmd.getType();
				if (cmd instanceof DataCommand) {
					((DataCommand) cmd).print(out);
					out.flush();
				} else if (type == Command.EXIT) {
					exiting = true;
					ExitCommand ecmd = (ExitCommand) cmd;
					System.exit(ecmd.getExitCode());
				} else if (type == Command.ERROR) {
					ErrorCommand ecmd = (ErrorCommand) cmd;
					Throwable cause = ecmd.getCause();
					if (cause != null) {
						cause.printStackTrace();
					}
				}
			}
		};
	}

	private static void registerExitHook(final Client client) {
		if (isDebug())
			debugPrint("registering shutdown hook");
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			public void run() {
				if (!exiting) {
					try {
						if (isDebug())
							debugPrint("sending exit command");
						client.sendExit(0);
					} catch (IOException ioexp) {
						if (isDebug())
							debugPrint(ioexp.toString());
					}
				}
			}
		}));
	}

	private static void registerSignalHandler(final Client client) {
		if (isDebug())
			debugPrint("registering signal handler for SIGINT");
		Signal.handle(new Signal("INT"), new SignalHandler() {
			public void handle(Signal sig) {
				try {
					con.printf("Please enter your option:\n");
					con.printf("\t1. exit\n\t2. send an event\n\t3. send a named event\n");
					con.flush();
					String option = con.readLine();
					option = option.trim();
					if (option == null) {
						return;
					}
					if (option.equals("1")) {
						System.exit(0);
					} else if (option.equals("2")) {
						if (isDebug())
							debugPrint("sending event command");
						client.sendEvent();
					} else if (option.equals("3")) {
						con.printf("Please enter the event name: ");
						String name = con.readLine();
						if (name != null) {
							if (isDebug())
								debugPrint("sending event command");
							client.sendEvent(name);
						}
					} else {
						con.printf("invalid option!\n");
					}
				} catch (IOException ioexp) {
					if (isDebug())
						debugPrint(ioexp.toString());
				}
			}
		});
	}

	private static void usage() {
		System.err.println(Messages.get("btrace.usage"));
		System.exit(1);
	}

	private static boolean isDebug() {
		return DEBUG;
	}

	private static boolean isUnsafe() {
		return UNSAFE;
	}

	private static void debugPrint(String msg) {
		System.out.println("DEBUG: " + msg);
	}

	private static void errorExit(String msg, int code) {
		exiting = true;
		System.err.println(msg);
		System.exit(code);
	}

	public static String getPwd() {
		return pwd;
	}

	public static void setPwd(String pwd) {
		Main.pwd = pwd;
	}
}
